home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Games of Daze
/
Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso
/
x2ftp
/
msdos
/
mxlibs
/
vatpm051
/
sound.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-04-30
|
111KB
|
3,122 lines
/**************************************************************************
VARMINT'S AUDIO TOOLS
Source code: SOUND.C
Turbo C++ V 3.1
Be careful about the memory model! The definition for
SAMPLE will define what model you should use.
Turn off stack checking and stack warning options in your
compiler.
Written by: Eric Jorgensen (April, 1995)
smeagol@rt66.com
Adapted from original Source by Peter Sprenger
sound.c is an adaptation (by Eric Jorgensen) from Peter Sprenger's
SoundX library. Eric's modifications (In part) are as follows:
- Removal of all VOC, PLAY, and MIXER functions
- reprogramming of DMA functions
- Addition of MIDI functions
- Addition of an interrupt driven sound handler
- Addition of numerous comments (about 98% of all comments are Eric's)
- Consolidation of all functions into a single file
- Rewriting of some of Peter's original functions to improve
readability and performance.
- Addition of MOD functions
- MPU-401 output and setup
- Removal of inline asembly functions
- CLeanup of Sound Blaster Autoinitialization code
As of April 19, about the only code left that is Peter's is some of the
Auto initialization code and some of the FM functions.
----------------------------------------------------------------------
Peter Sprenger's Original Copywrite is as follows:
* Copyright 1993 by Peter Sprenger Pete@amber.dinoco.de
* 5014 Kerpen 3
* Germany
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies. The author Peter Sprenger
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
----------------------------------------------------------------------
In the spirit of Peter's effort, I am offering my modification of his
library as FREEWARE. You are free to use it and distribute this
library, but you may charge no fee for it. If you construct another
sound library based on this one, it must be freeware, too. This
restriction does not apply to programs that only use this library to
generate audio output. (ie: if you make a game that uses this
library for sound generation, you can charge all the money
you want for your game.)
**** WARNING ****
Use Varmint's Audio tools at your own risk. This code has only been lightly
tested and has been found to cause cancer in laboratory rats.
**************************************************************************/
#include "sound.h"
#include "dos.h"
#define CINTNO 0
#define SLAVEPIC 2
#define RTCINTNO 8
#define WORD unsigned short
#define BYTE unsigned char
#define TD midi_data->track[i] + trkloc[i]
#define DOWN_SEMITONE 1.059463
#define UP_SEMITONE 0.943874
#define DOSYNC if(sync_on && inp(0x3da)&0x08) vsyncclock = 1000
#define DMAAUTOINIT 0x58
#define DMAONESHOT 0x48
//-------------------------------- Internal Function prototypes
void MPU_Write(BYTE b);
void interrupt _saveregs sb_int(void);
static cardtype CheckHard(void);
static short test_int(void);
static short scan_int(void);
static short FM_Detect(void);
BYTE FM_Status(void);
short DSP_Reset(void);
BYTE DSP_Read(void);
void DSP_Write(BYTE output);
WORD DSP_GetVersion(void);
void SB_SetVect(void);
void SB_RemoveVect(void);
short get_sb_env(void);
short CardCheck(void);
cardtype WhichCard(void);
BYTE int2vect(BYTE intnr);
void enable_int(BYTE nr);
void disable_int(BYTE nr);
void InitT2(void);
void measure(void);
void dma_set(BYTE *sound_address,WORD len,BYTE channel);
WORD polldma(BYTE channel);
short ReadVarLen(BYTE *data,int *value);
int ReadLong(FILE *infile);
short ReadShort(FILE *infile);
void MidiPlayer(void);
short getvoice(VOICE v[],short track,short channel, short note);
void dotick(void);
void dodiv(void);
//-------------------------------- Global function pointers
// These are used by the interrupt routines
// To help keep track of interrupts that
// Get shuffled around.
void (interrupt *handlerint)(void) = sb_int;
static void (interrupt *orgint)(void) = NULL;
static void (interrupt *orgtick)(void)= NULL;
static void (interrupt *orgirqint)(void) = NULL;
static void (*call_func)(void);
//-------------------------------- FM variables and data
static WORD FM_off[9]={0,0x100,0x200,0x800,0x900,0xa00,0x1000,0x1100,0x1200};
static BYTE FM_fnr[12]={0x57,0x6b,0x81,0x98,0xb0,0xca,0xe5,0x02,0x20,0x41,0x63,0x87};
static BYTE FM_key_or[12]={1,1,1,1,1,1,1,2,2,2,2,2};
static BYTE FM_key[9],FM_keyscale1[9],FM_keyscale2[9];
static BYTE FM_vol[9] = {0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f};
//-------------------------------- setup data and variables
static WORD ioaddr[6]={0x220,0x240,0x210,0x230,0x250,0x260};
static BYTE intrx[8]={5,7,10,11,12,15,2,3};
int realhandle;
WORD io_addr= 0x220,intnr= 5,card_id=1,fm_addr;
int dma_ch=1;
WORD fm_left,fm_right,fm_both,dsp_vers;
static WORD mue3,mue23,wh_card,rythm=0xbd00;
static WORD dma_adr[8]= {0x00,0x02,0x04,0x06,0xc0,0xc4,0xc8,0xcc};
static WORD dma_len[8]= {0x01,0x03,0x05,0x07,0xc2,0xc6,0xca,0xce};
static WORD dma_page[8]={0x87,0x83,0x81,0x82,0x8f,0x8b,0x89,0x8a};
SBERROR sberr = 0;
char *errname[] = {
"Cannot detect FMchip",
"Cannot detect DSP",
"Cannot find an open IRQ",
"Cannot find an open DMA channel",
"Cannot allocate memory for DMA buffer"};
volatile tst_cnt;
short mpu_available = FALSE;
BYTE DMA_controlbyte = DMAAUTOINIT;
//-------------------------------- DMA/DSP mixer varaibles and data
WORD dma_bufferlen = 60;
BYTE *dma_buffer = NULL;
BYTE *playahead_buffer = NULL;
short *mix_buffer = NULL;
short sounds_in_queue = 0;
short sample_rate = 11000;
SAMPLE *sounddata[MAXSOUNDS];
DWORD soundpos[MAXSOUNDS];
//-------------------------------- Timer varaibles
static WORD timer_val,timer_hold,timer_diff,mue999;
static WORD timadd,timsum;
//-------------------------------- MOD varaibles
MOD *mod_data = NULL;
short mod_on = FALSE,mod_reset = FALSE,mod_tablepos=0;
BYTE *mod_pattern,*mod_pstop,*mod_loopspot=NULL;
short mod_loopcount = -1;
short mod_bytespertick = 220,mod_ticksperdivision = 6;
short mod_currentbyte = 0,mod_currenttick = 0;
short mod_divoffset =0;
short mod_glissando = FALSE;
short mod_finetune = 0;
short mod_patterndelay = 0;
short mod_volume = 7;
BYTE channel_select[4] = {1,1,1,1};
CHANNEL chan[4];
//-------------------------------- MIDI/MPU varaibles
MIDI *midi_data = NULL;
short midi_reset = TRUE;
short midi_on = FALSE;
short midi_mpuout = FALSE;
short midi_fmout = TRUE;
short midi_dumbmode = TRUE;
short mpu_timeout;
WORD midi_port = 0x330;
float midi_callfreq = 1.0;
float midi_usertempo = 2.0;
float midi_tempoadjust = 2.0;
BYTE music_volume = 0x32; // +ECODE change me to midi_volume
DWORD vclock=0;
static BYTE defaultpatchmap[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
BYTE *midi_patchmap;
//-------------------------------- Miscellaneous varaibles
short debugnum=0;
short debug_antistatic = TRUE;
short debug_intdisable = FALSE;
WORD DSP_overhead = 0;
extern unsigned _stklen = 16000; // The stack is usually only 4K, but
// I was getting stack overflow problems
// with that ammount.
WORD vsync_toolong = 10; // This is the number of vclock ticks that
// tells us we have waited to long in
// the VarmintVSync() function.
WORD vsyncclock = 0;
BYTE sync_on = FALSE;
/**************************************************************************
void Go_Varmint(void)
void Dropdead_Varmint(void)
DESCRIPTION: Starts/stops the interrupt routine for Varmint's audio tools
**************************************************************************/
void Go_Varmint(void)
{
SB_SetVect(); // Install the sound kernel
if(card_id > 2) { // SB2.0 or better?
DSP_Write(0x48); // Set DSP for 8bit Autoinit DMA
DSP_Write((dma_bufferlen) & 0xff); // Write length low byte
DSP_Write((dma_bufferlen) >> 8); // Write length high byte
dma_set(dma_buffer,dma_bufferlen,dma_ch);
DSP_Write(0x1C); // Set DSP for Autoinit 8bit DMA
}
else { // SB 1.x?
DMA_controlbyte = DMAONESHOT;
}
DSP_Write(DSP_INVOKE_INTR); // Ignition!
}
void Dropdead_Varmint(void)
{
DSP_Write(0xD0); // Halt DMA
if(card_id>2) DSP_Write(0xDA); // Halt Autoinitialized DMA
DSP_Write(0xD0); // Halt DMA
SB_RemoveVect(); // Clean up sound kernel
real_free(realhandle); // Deallocate the memory for DMA buffer to be freed
}
/* --------------- FM Stuff ------------ */
/**************************************************************************
void FM_Write(WORD data)
DESCRIPTION: Writes a byte to the FM chip. The high byte is the
register, the low byte is the data.
**************************************************************************/
void FM_Write(WORD data)
{
BYTE reg,value;
reg = (data & 0xff00)>>8; // extract register and data value
value = data & 0x00ff;
outp(fm_addr,reg); // Write the register
mdelay(mue3); // Wait three microseconds
outp(fm_addr+1,value); // Write the data
mdelay(mue3); // Wait 23 microseconds
}
/**************************************************************************
void FM_Reset()
DESCRIPTION: Resets the FM chip by clearing all the registers then
setting a few appropriate bits.
**************************************************************************/
void FM_Reset(void)
{
WORD i;
for(i = 0; i <= 0xf500 ; i+= 0x100) FM_Write(i);
FM_Write(0x0120); // Turn on Wave form control
FM_Write(0xbdc0); // Set AM and Vibrato to high
}
/**************************************************************************
BYTE FM_Status()
DESCRIPTION: Reads the status byte of the FM chip
**************************************************************************/
BYTE FM_Status(void)
{
return (inp(fm_addr));
}
/**************************************************************************
static short FM_Detect()
DESCRIPTION: Detects the presence of an FM chip
**************************************************************************/
static short FM_Detect(void)
{
FM_Write(0x0100); /* init Test register */
FM_Write(0x0460); /* reset both timer */
FM_Write(0x0480); /* enable interrupts */
if(FM_Status() & 0xe0) return(FALSE);
FM_Write(0x02ff); /* write ffh to timer 1 */
FM_Write(0x0421); /* start timer 1 */
if(fm_addr==0x388) msdelay(21); /* wait 21000 mcs */
else mdelay(mcalc(80)); /* wait at least 80 microsec */
if((FM_Status() & 0xe0)!=0xc0) return(FALSE);
FM_Write(0x0460); /* reset both timer */
FM_Write(0x0480); /* enable interrupts */
return(TRUE);
}
/**************************************************************************
void FM_SetVoice(BYTE voice,BYTE *ins)
DESCRIPTION: Sets the voice from an 11 byte array
BYTE ID
0 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 1)
1 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 2)
2 Key level scaling/ total level (oper 1)
3 Key level scaling/ total level (oper 2)
4 Attack Rate/ Decay rate (oper 1)
5 Attack Rate/ Decay rate (oper 2)
6 Sustain Level/ Release rate (oper 1)
7 Sustain Level/ Release rate (oper 2)
8 Feedback / Algorythm (oper 1&2)
9 Wave Form Select (oper 1)
10 Wave Form Select (oper 2)
**************************************************************************/
void FM_SetVoice(BYTE voice,BYTE *ins)
{
if(voice > 8) return;
FM_keyscale1[voice]=ins[2] & 0xc0; // store key scaling for FM_Vol
FM_keyscale2[voice]=ins[3] & 0xc0;
// Write voice data
FM_Write((0x2000 + FM_off[voice]) | ins[0]);
FM_Write((0x2300 + FM_off[voice]) | ins[1]);
// For the next two, we want to
// make sure current volume is
// preserved.
FM_Write((0x4000 + FM_off[voice]) | (ins[2] & 0xc0) | FM_vol[voice]);
FM_Write((0x4300 + FM_off[voice]) | (ins[3] & 0xc0) | FM_vol[voice]);
// the rest of the voice is just
// straight writes.
FM_Write((0x6000 + FM_off[voice]) | ins[4]);
FM_Write((0x6300 + FM_off[voice]) | ins[5]);
FM_Write((0x8000 + FM_off[voice]) | ins[6]);
FM_Write((0x8300 + FM_off[voice]) | ins[7]);
FM_Write((0xc000 + voice * 0x100) | ins[8]);
FM_Write((0xE000 + FM_off[voice]) | ins[9]);
FM_Write((0xE300 + FM_off[voice]) | ins[10]);
}
/**************************************************************************
void FM_SetFreq(BYTE voice,short freq)
DESCRIPTION: sets an explicit pseudo frequency (0 - 0xffff)
Note: There is no way to really set a direct frequency on an FM
chip, so I wrote this routine which is based on octaves, so I
imagine it is slightyl non-linear. Still, it is good for
special effects.
**************************************************************************/
void FM_SetFreq(BYTE voice,WORD freq)
{
BYTE highbits,lowbits;
WORD data,frac;
short octave;
octave = (freq / 0x2000); // Extract octtave number (0-7)
// convert remaining fraction into a
// 10 bit value.
frac = ((double)(freq - octave * 0x2000)/(double)0x2000) * 0x157 + 0x157;
highbits = (frac & 0x300) >> 8; // divide fraction into low and high bits
lowbits = frac & 0xff;
data=0xa000+(voice<<8)|lowbits; // store low bits for now
FM_key[voice]=highbits|(octave<<2); // save high bits for Key_on(); (octave 4)
FM_Write(data); // write low bits to FM chip;
}
/**************************************************************************
void FM_SetNote(BYTE voice,BYTE note)
DESCRIPTION: sets the frequency for a chromatic note
**************************************************************************/
void FM_SetNote(BYTE voice,BYTE note)
{
BYTE blk,notex;
WORD data;
// calculate freq number and octave
notex=note-24;
blk=1;
while(notex>=12)
{
notex-=12;
blk++; // octave number
}
data=0xa000+(voice<<8)|FM_fnr[notex];
FM_key[voice]=FM_key_or[notex]|(blk<<2); // save part of the note for Key_on()
FM_Write(data); // write note to the chip
}
/**************************************************************************
void FM_SetVol(BYTE voice,BYTE vol)
DESCRIPTION: The the volume (0-63) for a voice.
**************************************************************************/
void FM_SetVol(BYTE voice,BYTE vol)
{
if (voice >8) return;
// Convert volume from a logical value to
// the value that is really used. ie: 3f is
// really the quietest setting, while 0 is
// the loudest. Weird, eh?
FM_vol[voice] = (0x3f - (vol & 0x3f));
// Write the volume while preserving the
// other important parts of the voice.
FM_Write((0x4000+FM_off[voice]) |FM_vol[voice] | FM_keyscale1[voice]);
FM_Write((0x4300+FM_off[voice]) |FM_vol[voice] | FM_keyscale2[voice]);
}
/* A NOTE ABOUT RYTHM FUNCTIONS:
I've only played around with these functions a little bit. Here are some
things that I've learned:
- only channels 6,7,and 8 are affected by the rythm mode.
- You will need to develop special instrument definitions to get
the rythm instruments to sound right. The most important parameters
in a rythm instrument definition are attack/decay/sustain rates and
the waveform (bytes 9 and 10).
- channels 6,7, and 8 each behave differently in rythm mode:
6 - Instrumental. Sounds like a triangle
7 - White noise. Sounds like a snare drum
8 - High white noise. Sounds like a Cymbal.
- If you want to add white noise effects to your program (Gun shots
engines, etc...) channel 7 in rythm mode is a good source.
- ERIC
*/
/**************************************************************************
void FM_RythmMode(BYTE bool)
DESCRIPTION: Turns on/off rythm mode based on input.
**************************************************************************/
void FM_RythmMode(BYTE bool)
{
WORD data;
if(bool) data=0xbde0;
else data=0xbdc0;
rythm=data; // This global keeps track of the
// mode for other rythm functions.
FM_Write(data);
}
/**************************************************************************
void FM_RythmOn(BYTE inst)
DESCRIPTION: Turns on a Specified rythm instrument. You should use these
definitions:
FM_HIHAT
FM_TOPCYM
FM_TOMTOM
FM_SNARE
FM_BASS
**************************************************************************/
void FM_RythmOn(BYTE inst)
{
rythm|=inst;
FM_Write(rythm);
}
/**************************************************************************
void FM_RythmOff(BYTE inst)
DESCRIPTION: Turns off a Specified rythm instrument. You should use these
definitions:
FM_HIHAT
FM_TOPCYM
FM_TOMTOM
FM_SNARE
FM_BASS
**************************************************************************/
void FM_RythmOff(BYTE inst)
{
rythm&=(~inst);
FM_Write(rythm);
}
/**************************************************************************
void FM_KeyOn(BYTE voice)
DESCRIPTION: Turn on an FM voice.
This description is misleading, since in my experirnce,
FM voices are always on. This function really just
triggers the FM voice.
**************************************************************************/
void FM_KeyOn(BYTE voice)
{
WORD data;
if(voice > 8) return;
data=0xb000+(voice<<8); // set write address
data |= FM_key[voice]|0x20; // set key on bit and frequency
FM_Write(data);
}
/**************************************************************************
void FM_KeyOff(BYTE voice)
DESCRIPTION: Turn off an FM voice.
(See FMKeyOn) Again, I've found that voices are always
on, and to turn them off you really need to just set the
volume to 0. Turning off the Key_on bit may prepare
the voice for a trigger, though.
**************************************************************************/
void FM_KeyOff(BYTE voice)
{
WORD data;
if(voice > 8) return;
data=0xb000+(voice<<8); // set address
data |= FM_key[voice]; // preserve frequency data
FM_Write(data);
// working.
}
/* --------------- DSP Stuff ------------ */
/**************************************************************************
short DSP_Reset()
DESCRIPTION: Resets the DSP
**************************************************************************/
short DSP_Reset(void)
{
short i;
outp(io_addr+DSP_RESET,1); // Write a 1 to the DSP reset port
mdelay(mue3); // Wait 3 microseconds
outp(io_addr+DSP_RESET,0); // Write a 0 to the DSP reset port
for(i=0;i<50;i++) { // DSP should send back an 0xaa
mdelay(mue3);
if(DSP_Read()==0xaa) return(TRUE);
}
return(FALSE);
}
/**************************************************************************
BYTE DSP_Read()
DESCRIPTION: reads a byte from the dsp
**************************************************************************/
BYTE DSP_Read(void)
{
// Read until high bit of status port
// is set.
while(!(inp(io_addr+DSP_RSTATUS) & 0x80));
return (inp(io_addr+DSP_READ)); // Send back the value of the read port
}
/**************************************************************************
short MPU_Reset(void)
DESCRIPTION: This function resets the MPU-401 chip and reports wether
or not it was succesful by returning a boolen.
RETURNS:
True if successful, False if not
**************************************************************************/
short MPU_Reset(void)
{
short success = TRUE;
outp(midi_port+1,0xFF); // Send reset command
mpu_timeout = 0;
while(mpu_timeout<1000) { // Wait for ready bit to clear
mpu_timeout++;
if(!(inp(midi_port+1)&0x80)) break;
}
if(mpu_timeout == 1000) { // Did it Time out?
outp(midi_port+1,0xFF); // Try again
mpu_timeout = 0;
while(mpu_timeout<1000) { // Wait for ready bit to clear
mpu_timeout++;
if(!(inp(midi_port+1)&0x80)) break;
}
if(mpu_timeout == 1000) success = FALSE; // Two timeouts and you're out
}
outp(midi_port+1,0x3F); // Put MPU in Uart Mode
mpu_timeout = 0;
while(mpu_timeout<1000) { // Wait for ready bit to clear
mpu_timeout++;
if(!(inp(midi_port+1)&0x80)) break;
}
if(mpu_timeout == 1000) success = FALSE; // No UART? No MIDI.
return(success);
}
/**************************************************************************
void MPU_Write(BYTE b)
DESCRIPTION: Writes a byte the midi data port
**************************************************************************/
void MPU_Write(BYTE b)
{
mpu_timeout = 0;
while(mpu_timeout<1000) { // Wait for ready bit to clear
mpu_timeout++;
if(!(inp(midi_port+1)&0x40)) break;
}
outp(midi_port,b); // Write the byte!
}
/**************************************************************************
void DSP_Write(BYTE output)
DESCRIPTION: Writes a byte to the DSP
**************************************************************************/
void DSP_Write(BYTE output)
{
// Read until high bit of status port
// is clear.
while((inp(io_addr+DSP_WSTATUS) & 0x80));
outp(io_addr+DSP_WRITE,output); // Write our byte
}
/**************************************************************************
short get_sb_env()
DESCRIPTION: Get sound blaster information from the environment
variable "BLASTER"
**************************************************************************/
short get_sb_env(void)
{
char *str;
short i;
short outvalue = TRUE;
str=getenv("BLASTER");
if(!str) return(FALSE); // no blaster variable? go home
// Convert string to upper case
for(i = 0 ; i < strlen(str); i++) *(str+i) = toupper(*(str+i));
// pick apart variable for info.
// Io address
for(i = 0; *(str+i) != 0 && *(str + i) != 'A'; i++);
if(*(str+i)){
sscanf(str+i+1,"%hx",&io_addr);
if(io_addr<0x210 || io_addr>0x260) outvalue = FALSE;
}
// MIDI port address
for(i = 0; *(str+i) != 0 && *(str + i) != 'P'; i++);
if(*(str+i)) sscanf(str+i+1,"%hx",&midi_port);
// Dma channel number
for(i = 0; *(str+i) != 0 && *(str + i) != 'D'; i++);
if(*(str+i)){
sscanf(str+i+1,"%hi",&dma_ch);
if(dma_ch > 7) outvalue = (FALSE); // only 0-7 allowed
}
// IRQ interrupt number
for(i = 0; *(str+i) != 0 && *(str + i) != 'I'; i++);
if(*(str+i)){
sscanf(str+i+1,"%hd",&intnr);
if(intnr < 2 || intnr > 10) outvalue = (FALSE);
}
// card_id
for(i = 0; *(str+i) != 0 && *(str + i) != 'T'; i++);
if(*(str+i)){
sscanf(str+i+1,"%hd",&card_id);
if(card_id < 1 && card_id > 6) outvalue = FALSE; // 1 = SB 1.x 6 = SB AWE
}
return(outvalue);
}
/**************************************************************************
WORD DSP_GetVersion()
DESCRIPTION: Get the version number of the DSP
**************************************************************************/
WORD DSP_GetVersion(void)
{
DSP_Write(DSP_GET_VERS);
return((WORD)DSP_Read()*256+DSP_Read());
}
/* --------------- Misc. Stuff ------------ */
/**************************************************************************
short CardCheck()
DESCRIPTION: Check for both FM chip and DSP
**************************************************************************/
short CardCheck(void)
{
short ret=0;
if(FM_Detect()) ret|=FM_DETECT;
if(DSP_Reset()) ret|=DSP_DETECT;
return(ret);
}
/**************************************************************************
static void far interrupt testn_int()
DESCRIPTION: This function is stored as an interrupt to test
various interrupt vectors by test_int()
**************************************************************************/
static void interrupt testn_int(void)
{
tst_cnt++; // increment our test counter
inp(io_addr+DSP_RSTATUS); // Acknowledge DSP interrupt
if(intnr < 8) outp(0x20,0x20); // Clear PIC
else outp(0xa0,0x20);
}
/**************************************************************************
static short test_int()
DESCRIPTION: This function is used by scan_int() to test interrupt
stuff. It installs a test interrupt in the
requested spot (intnr) then sees if the DSP can
use it.
**************************************************************************/
static short test_int(void)
{
short i;
BYTE int1,int2;
orgint=_dos_getvect(int2vect(intnr)); // Save original interrupt
int1 = inp(0x21); // Save PIC settings
int2 = inp(0xa1);
tst_cnt=0; // reset our test interrupt counter.
disable_int(intnr); // put in our test interrupt
_dos_setvect(int2vect(intnr),testn_int);
enable_int(intnr);
DSP_Write(DSP_INVOKE_INTR); // Force DSP interrupt
for(i=0;i<30000;i++) if(tst_cnt) break; // wait for interrupt code to happen
disable_int(intnr); // put original interrupt back
_dos_setvect(int2vect(intnr),orgint);
outp(0x21,int1); // restore PIC settings
outp(0xa1,int2);
if(i==30000) return(FALSE); // Timed out? No good!
else return(TRUE);
}
/**************************************************************************
static short scan_int()
DESCRIPTION: This makes sure that the interrupt number picked by the
IRQ specification is a good choice.
**************************************************************************/
static short scan_int(void)
{
short i;
if(test_int()) return(intnr); // Original choice good?
for(i=0;i<8;i++) // Try our eight best guesses
{
intnr=intrx[i];
if(test_int()) return(i);
}
return(0);
}
/**************************************************************************
static cardtype CheckHard()
DESCRIPTION: Checks hardware for DSP and FM chip
**************************************************************************/
static cardtype CheckHard(void)
{
short ret;
ret=DSP_Reset();
if(ret)
{
if(!scan_int()) { // Scan IRQ's
sberr= irqerr;
return(none);
}
fm_addr=io_addr+FM_BOTH_OFF;
if(!FM_Detect()) {
sberr = fmerr;
return(none); /* no fm? -> damaged! */
}
// SBPro checking here. Not too critical
/* fm_both=fm_addr;
fm_addr=io_addr+FM_RIGHT_OFF;
fm_right=fm_addr;
ret3=FM_Detect();
fm_addr=fm_both;
if(ret3)
{
wh_card=sbpro;
fm_left=io_addr+FM_LEFT_OFF;
}
else wh_card=sb20; */
wh_card = sb20;
return(wh_card);
}
sberr = nodsperr;
return(none);
}
/**************************************************************************
cardtype WhichCard()
DESCRIPTION: Calls various functions to make sure you've
got a Sound Blaster
**************************************************************************/
cardtype WhichCard(void)
{
cardtype cret = nodsp;
short i;
if(get_sb_env()) cret=CheckHard(); // grab environment variable
if(cret!=nodsp) return(cret); // If dsp is there, then go home
for(i=0;i<6;i++) // scan around for a better io address
{
io_addr=ioaddr[i];
cret=CheckHard();
if(cret!=nodsp) return(cret);
}
return(none); // Uh oh.
}
/**************************************************************************
short SB_Setup()
DESCRIPTION: Sets up the sound blaster for action. This is the only
function a programmer should really use. Most of the
nitty gritty is handled internally.
**************************************************************************/
short SB_Setup(void)
{
short i,length;
InitT2(); /* init Timer 2 */
measure(); /* time loop factor */
mue3=mcalc(3) ; /* calc val for 3 micro sec delay */
mue23=mcalc(23) ; /* calc val for 23 micro sec delay */
WhichCard(); // Go and check the hardware
if(wh_card==none) return(FALSE);
// Get DSP ready
dsp_vers=DSP_GetVersion();
DSP_Write(DSP_SPKR_ON);
// Allocate space for playback buffer
// For protected mode a special function is used to get REAL memory
// that the DMA can find
dma_buffer = (BYTE*)real_malloc(2*dma_bufferlen,&realhandle);
// dma_buffer = (BYTE*)low_malloc(2*dma_bufferlen,&realhandle);
if(!dma_buffer) {
sberr = nomem;
return(FALSE);
}
// Allocate space for the play ahead buffer
playahead_buffer = (BYTE *)malloc(dma_bufferlen+5);
if(!playahead_buffer) {
sberr = nomem;
return(FALSE);
}
// Allocate space for Mixing buffer
mix_buffer = (short *)malloc(dma_bufferlen*2+10);
if(!mix_buffer) {
sberr = nomem;
return(FALSE);
}
// Clear the buffer
for(i = 0; i < dma_bufferlen+4; i++) {
*(dma_buffer+i) = 127;
*(playahead_buffer+i) = 127;
}
SetRate(11000); // Set the sample rate
//mpu_available = MPU_Reset(); // Check for an MPU chip
midi_patchmap = defaultpatchmap;
return(TRUE);
}
/**************************************************************************
DWORD far2long(char far *adr)
DESCRIPTION: This is used by dma_set to convert a regular far address
to a 20 bit flat address.
**************************************************************************/
DWORD far2long(char *adr)
{
return(((DWORD)FP_SEG(adr)<<4)+FP_OFF(adr));
}
/**************************************************************************
void SetRate(WORD rate)
DESCRIPTION: Sets the sample rate (specified in hz)
**************************************************************************/
void SetRate(WORD rate)
{
DWORD val;
if(rate<4000) return; // Calculate number for the sound card
val=256-1000000L/rate;
DSP_Write(DSP_SAMPLE_RATE);
DSP_Write((BYTE)val);
sample_rate = rate; // FYI - This helps other functions
// To know how fast the DSP is going
}
/**************************************************************************
WORD dma_set(DWORD adrl,WORD len,short channel)
DESCRIPTION: This programs the DMA controller to start a single pass
output transfer.
(Draeden of VLA has provided some good information for
DMA programming in his INTRO to DMA document)
**************************************************************************/
void dma_set(BYTE *sound_address,WORD len,BYTE channel)
{
WORD adr;
DWORD adrl;
BYTE page;
// adrl = far2long(sound_address); // convert address to 20 bit format
adrl=(int)sound_address; //don't need to convert already have 20 bit form
adr=(WORD)adrl; // extract page address
page=(BYTE)(adrl>>16); // extract page number
// PREPARE DMA.
// (Channels 0-3 have different
// command ports than 4-7.)
// SET CHANNEL, MASK BIT AND MODE
if(channel <4) { // Chanels 0-3?
outp(0x0a,channel+4); // write channel number with 3rd bit set
outp(0x0c,0); // Clear Byte Pointer
// Set the mode. The mode is determined
// in Go_Varmint().
outp(0x0b,DMA_controlbyte+channel);
}
else { // channels 4-7
outp(0xd4,channel); // write channel number
outp(0xd8,0); // Clear Byte Pointer
// Set the mode. The mode is determined
// in Go_Varmint().
outp(0xd6,DMA_controlbyte+channel);
}
// SET TRANSFER INFORMATION
outp(dma_adr[channel],adr&0xff);// Write address low byte
outp(dma_adr[channel],adr>>8); // Write address high byte
outp(dma_len[channel],len&0xff);// Write length low byte
outp(dma_len[channel],len>>8); // Write length high byte
outp(dma_page[channel],page); // Write page
// CLEAR MASK BITS
if(channel < 4) outp(0x0a,channel);
else outp(0xd4,channel & 0x03);
}
/**************************************************************************
void polldma(BYTE channel)
DESCRIPTION: This function poles the DMA controller to find out how many
bytes are left in the current transfer.
As of version 0.4, this function is no longer used, but
I thought it might be useful to someone else, so I've only
commented it out.
**************************************************************************/
/*WORD polldma(BYTE channel)
{
BYTE low1,high1,low2,high2;
disable_int(intnr); // Turn off the interrupt so we don't get
// caught with our pants down.
asm {
mov dx,0x0c // Flip the master reset switch
mov al,0
out dx,al
}
_DX = dma_len[channel]; // Load in the counter address
// read position twice, becasue sometimes
// there is a problem
asm{
in al,dx // read the low byte first
mov low1,al
in al,dx // read the high byte next
mov high1,al
in al,dx // read the low byte first
mov low2,al
in al,dx // read the high byte next
mov high2,al
}
enable_int(intnr); // Done, so we'll put the interrupt back.
// High bytes the same? Use second reading
if(high1 == high2)return((WORD)high2*256+low2);
// else the First reading is accurate
return(high1*256+low1);
}*/
/**************************************************************************
static void far interrupt sb_int()
DESCRIPTION: This is the sound Blaster interrupt that is to be
called at the end of DMA transfer. This is how the flow
of things goes:
Dump last mix to the DMA buffer
Mix regular samples
Mix MOD stuff
Calculate next DMA buffer
acknowledge interrupt
Run the midi stuff.
The DOSYNC's are macros define in sound.h designed to
check the vertical retrace and then set a byte so that
these sound functions can be used with games that check
the vertical retrace.
**************************************************************************/
void _saveregs interrupt sb_int(void)
{
short i,j;
WORD lastbyte;
short *mbuf;
BYTE *dbuf;
static int midi_count = 100;
if(debug_intdisable) _disable();
inp(io_addr+DSP_RSTATUS); // Acknowledge DSP interrupt
// if(DSP_overhead) timer_on(); // start timer to measure DSP overhead
// Copy in playahead section of the buffer
memcpy(dma_buffer,playahead_buffer,dma_bufferlen+2);
DOSYNC; // Macro for checking the vertical retrace
// clear out the mixing buffer
for(j = 0; j < dma_bufferlen+1; j++) *(mix_buffer+j) = 128;
DOSYNC;
// Mix in digitized sounds
for(i = 0; i <sounds_in_queue; i++) {
DOSYNC;
if(soundpos[i]>=dma_bufferlen) {// enough left to fill the buffer?
for(j = 1; j < dma_bufferlen+1; j++) *(mix_buffer+j) += *(sounddata[i]++);
soundpos[i] -= (dma_bufferlen);
}
else { // else play what is left
for(j = 1; j <= soundpos[i]; j++) *(mix_buffer+j) += *(sounddata[i]++);
// delete the sound
for(j = i; j < sounds_in_queue;j++) {
sounddata[j] = sounddata[j+1];
soundpos[j] = soundpos[j+1];
}
sounds_in_queue--; // One less sound.
i--; // make sure we don't skip the next sound
}
}
DOSYNC;
// Check to make sure that dma_bufferlen
// is not bigger than the number of bytes
// between ticks. If it is, this loop
// will crash.
if(mod_bytespertick < dma_bufferlen) mod_on = FALSE;
if(mod_on && mod_data) { // MOD stuff activated and ready?
if(mod_reset) { // Reset the MOD?
for(i = 0; i < 4; i++ ) { // Reset all four channels
chan[i].sdata = NULL;
chan[i].offset = 0;
chan[i].rlength = 0;
chan[i].end = 0;
chan[i].volume = 0;
chan[i].sample_number = 0;
chan[i].position= 0;
chan[i].counter = 0;
chan[i].pinc = 0;
chan[i].effect = 0;
chan[i].x = 0;
chan[i].y = 0;
}
// Reset global mod variables
mod_pattern=mod_data->pattern_data[mod_data->ptable[0]];
mod_pstop = mod_pattern+1024;
mod_tablepos=0;
mod_bytespertick = sample_rate/11000.0 * 220;
mod_ticksperdivision = 6;
mod_currentbyte = 0;
mod_currenttick = 0;
mod_divoffset =0;
mod_reset = FALSE; // Reset the reset switch
}
// **************************************
// This little section mixes all the mod
// channels if the number of bytes left
// to go until the next tick is bigger
// than the dma_buffer length.
// **************************************
if(mod_currentbyte > dma_bufferlen) {
for(i = 0; i < 4; i++) { // Loop through the four channels
DOSYNC;
// If the volume is zero or if the sample
// has ended, then there is no need to mix
// anything.
if(chan[i].volume && chan[i].position <= chan[i].end) {
// Loop along the length of the mixing buffer
for(j = 0; j < dma_bufferlen+1; j++) {
// Add this byte to the mixing buffer
*(mix_buffer+j) += (*(chan[i].sdata + chan[i].position) * chan[i].volume)>>mod_volume;
// Here is where the frequency magic
// happens. I use fixed point arithmatic
// to increment the sample position pointer
// at different rates.
chan[i].counter += chan[i].pinc;
chan[i].position += chan[i].counter>>8;
chan[i].counter &= 0x00ff;
// Are we at the end of the sample?
if(chan[i].position >= chan[i].end) {
// Repeating? Move the position back.
if(chan[i].rlength > 1) {
chan[i].position = chan[i].offset;
chan[i].counter = 0;
chan[i].end = chan[i].offset+chan[i].rlength;
}
else break; // else Jump out of mixing loop
}
}
}
}
// Keep track of where we are.
mod_currentbyte -= dma_bufferlen+1;
}
// **************************************
// This little section mixes all the mod
// channels if the number of bytes left
// to go until the next tick is *smalleer*
// than the dma_buffer length.
//
// It first mixes what is left, then it
// decrements the tick and calls out
// to the module handling routines.
// Once that is donde, it mixes the
// rest of the DMA buffer.
//
// The two mixing routines are extacly the
// same as the one above except the first
// one loops over the first part of the
// mixing buffer, and the second mixes
// over the rest.
// **************************************
else {
// Mix the first part of the buffer
for(i = 0; i < 4; i++) {
DOSYNC;
if(chan[i].volume && chan[i].position <= chan[i].end) {
for(j = 0; j < mod_currentbyte+1; j++) {
*(mix_buffer+j) += (*(chan[i].sdata + chan[i].position) * chan[i].volume)>>mod_volume;
chan[i].counter += chan[i].pinc;
chan[i].position += chan[i].counter>>8;
chan[i].counter &= 0x00ff;
if(chan[i].position >= chan[i].end) {
if(chan[i].rlength > 1) {
chan[i].position = chan[i].offset;
chan[i].counter = 0;
chan[i].end = chan[i].offset+chan[i].rlength;
}
else break;
}
}
}
}
lastbyte = mod_currentbyte+1; // Keep track of where we are inthe buffer
// Ready for next division?
if(mod_currenttick >= mod_ticksperdivision * (mod_patterndelay+1) ) {
mod_patterndelay = 0; // Reset pattern delay
dodiv(); // Handle the division commands.
DOSYNC;
// Are we at the end of the pattern?
if(mod_pattern == mod_pstop) {
mod_tablepos++; // Next table!
// Done with tables? Done with MOD.
if(mod_tablepos == mod_data->num_positions) mod_on = FALSE;
else { // Otherwise, set pointer to next table
mod_pattern = mod_data->pattern_data[mod_data->ptable[mod_tablepos]];
mod_pstop = mod_pattern+1024;
}
}
mod_currenttick = 0; // Reset the tick counter
}
// Reset the byte counter
mod_currentbyte = mod_bytespertick;
mod_currenttick++; // Do our tick stuff.
dotick();
DOSYNC;
// Mix the rest of the buffer
for(i = 0; i < 4; i++) {
DOSYNC;
if(chan[i].volume && chan[i].position <= chan[i].end) {
for(j = lastbyte; j < dma_bufferlen+1; j++) {
*(mix_buffer+j) += (*(chan[i].sdata + chan[i].position) * chan[i].volume)>>mod_volume;
chan[i].counter += chan[i].pinc;
chan[i].position += chan[i].counter>>8;
chan[i].counter &= 0x00ff;
if(chan[i].position >= chan[i].end) {
if(chan[i].rlength > 1) {
chan[i].position = chan[i].offset;
chan[i].counter = 0;
chan[i].end = chan[i].offset+chan[i].rlength;
}
else break;
}
}
}
}
// Update our position
mod_currentbyte -= (dma_bufferlen-lastbyte +1);
}
DOSYNC;
}
// Write mixed stuff to the playback buffer
for(mbuf = mix_buffer, dbuf = playahead_buffer;
mbuf != mix_buffer+dma_bufferlen+2;
mbuf++) {
if(*mbuf > 254) *mbuf = 254; // Clip overload values
else if(*mbuf < 1) *mbuf = 1;
*(dbuf++) = *mbuf;
}
DOSYNC;
// * BLACK MAGIC * Setting this byte
// prevents static that seems to
// be caused when other interrupts
// get interrupted by this one.
if(debug_antistatic) *dma_buffer = *(dma_buffer+dma_bufferlen);
if(card_id < 3) { // Not SB 2.0?
// Set up DMA for one-shot
dma_set(dma_buffer,dma_bufferlen,dma_ch);
DSP_Write(0x14); // One shot DMA DAC (8bit)
DSP_Write((dma_bufferlen) & 0xff); // Write length low byte
DSP_Write((dma_bufferlen) >> 8); // Write length high byte
}
DOSYNC;
// Varmint's system clock
vclock ++; // Vertical retrace sync clock
vsyncclock ++;
// MIDI stuff first
midi_count-= 100;
if(midi_count < 100) {
MidiPlayer(); // Call a midi player
midi_count += midi_callfreq * midi_usertempo*100.0; // reset counter
DOSYNC;
}
// if(DSP_overhead) DSP_overhead = timer_off();// How long did this take?
if(debug_intdisable) _enable();
if(intnr < 8) outp(0x20,0x20); // Clear PIC
else outp(0xa0,0x20);
}
/**************************************************************************
void SB_SetVect()
DESCRIPTION: Installs the DMA interrupt vector. This makes it so that
sb_int() is called whenever a DMA transfer is finished
**************************************************************************/
void SB_SetVect(void)
{
orgirqint=_dos_getvect(int2vect(intnr));
_dos_setvect(int2vect(intnr),handlerint); /* set vector to our routine */
enable_int(intnr); /* enable sb interrupt */
}
/**************************************************************************
void SB_RemoveVect()
DESCRIPTION: Removes the DMA interrupt vector
**************************************************************************/
void SB_RemoveVect(void)
{
disable_int(intnr); /* disable sb interrupt */
_dos_setvect(int2vect(intnr),orgirqint); /* restore org intr vector */
}
/* --------------------------------------------------- */
/* timerX routines are following
These routines are for highly accurate time measurements
*/
/**************************************************************************
void InitT2()
DESCRIPTION: Initializes speaker timer for timing operations.
**************************************************************************/
void InitT2(void)
{
// Disable speaker output, but keep
// timer 2 enabled.
outp(0x61,(inp(0x61)&0xfd) | 0x01);
}
/**************************************************************************
void timer_on()
DESCRIPTION: Turns on timer counter for a time measurement
**************************************************************************/
void timer_on(void)
{
outp(0x43,0xb0); // Set up the timer for countdown
outp(0x42,0xff); // Least significant byte
outp(0x42,0xff); // Most significant byte
}
/**************************************************************************
WORD timer_off()
DESCRIPTION: Turns off time and reports clicks elapsed. Note that this
timer is so quick that it is wraps after only 56
milliseconds. If you want to timer longer stuff, I suggest
using the global variable vclock. It's tick frequency is
sample_rate / dma_bufferlen.
**************************************************************************/
WORD timer_off(void)
{
/*
outp(0x43,0xc0); // Read the timer countdown status
timer_hold = inp(0x42) + inp(0x42) * 256;
timer_diff = 0xffff - timer_hold; // timer_diff is used later
return timer_diff;
*/
}
/**************************************************************************
WORD to_micro(WORD clk)
DESCRIPTION: Converts clock ticks number to microsecs
**************************************************************************/
WORD to_micro(WORD clk)
{
return(clk*838/1000);
}
/**************************************************************************
void measure()
DESCRIPTION: measures a standard delay loop for other delay functions
**************************************************************************/
void measure(void)
{
register i=10000;
/* _disable(); // Disable interrupts
timer_on(); // Start the timer
while(i--); // Count to 10000
timer_val = timer_off(); // read the timer
_enable(); // Enable interrupts
*/
timer_val=1000;
mue999 = mcalc(990); // Calculate a millisecond
}
/**************************************************************************
void mdelay(WORD delay)
DESCRIPTION: Very tiny delay
**************************************************************************/
void mdelay(WORD delay)
{
register i = delay;
while(i--);
}
/**************************************************************************
void _saveregs msdelay(WORD delay)
DESCRIPTION: Millisec delay. When using this library, you should
use this delay for millisecond delays instead of the delay
function that comes with turbo C.
**************************************************************************/
void _saveregs msdelay(WORD delay)
{
WORD i;
for(i=0;i<delay;i++) mdelay(mue999); // mdelay(mue999) = 1 millisec
}
/**************************************************************************
WORD mcalc(WORD micro)
DESCRIPTION: Calculates number of ticks to send to mdelay for a specified
number of microseconds.
**************************************************************************/
WORD mcalc(WORD micro)
{
return(WORD)((long)micro*10000L/timer_val*1000/838);
}
/********************************
TIMER 0 STUFF
*********************************/
/* --------------------------------
CAUTION: These routines can cause a lot of headaches while debugging.
If you set your own interrupt and then stop the program before you call
Remove_Timer0(), you'd better reboot your computer, because very
unpredictable things will happen if Install_Timer0() is called again.
My suggestion is to get your interrupt working and then comment out the
Timer0 routines until the rest of the program is written and debugged.
- Eric */
/**************************************************************************
static void interrupt timerint()
DESCRIPTION: THis is the actual interrupt function stored in the timer
0 slot (short 08). This calls the old int08 function
at proper intervals as well as the user specified function
**************************************************************************/
static void interrupt timerint(void)
{
timer_on(); // set timer for overhead calculation.
call_func(); // user specified function
// Now let's do some fancy counting so
// we can call the system clock at the
// right moments.
if(timsum<100)
{
timsum+=timadd;
orgtick();
}
else
{
// asm mov al,0x20;
// asm out 0x20,al;
}
timsum-=100; // decrement our special timer
}
/**************************************************************************
void Install_Timer0(WORD period,void far (*func)())
DESCRIPTION: This sets up timer0 to call your function at the specified
period.
**************************************************************************/
void Install_Timer0(WORD period,void (*func)(void))
{
if(!func) return; /* no valid func ptr */
call_func=func;
timadd= (WORD)(6553600L/period); // counting seed for timerint()
timsum=0; // start counter at 0
// asm mov al,0x36 /* program timer 0 with modus 3 */
// asm out 0x43,al /* and counter value of period */
// asm mov ax,period
// asm out 0x40,al
// asm mov al,ah
// asm out 0x40,al
orgtick= _dos_getvect(8); // Remember the old interrupt
_dos_setvect(8,timerint); // put in a new one.
}
/**************************************************************************
void Remove_Timer0()
DESCRIPTION: Removes your goofy interrupt, 'cause we didn't want
it anyway! :P
**************************************************************************/
void Remove_Timer0(void)
{
if(!orgtick) return; // Must have called Install_Timer0 first
// asm mov al,0x36 /* program timer 0 with modus 3 */
// asm out 0x43,al /* and counter value of 0 (2^16)*/
// asm mov al,0
// asm out 0x40,al
// asm out 0x40,al
_dos_setvect(8,orgtick); // put back original vector
}
/**************************************************************************
BYTE int2vect(BYTE intnr)
DESCRIPTION: This function converts a PIC irq number to a true
interrupt vector number. For PC's with a 286 or greater,
irq's 0-7 refer to interrupts 0x08 - 0x0F and
irq's 8-15 refer to interrupts 0x70 - 0x77.
**************************************************************************/
BYTE int2vect(BYTE intnr)
{
if(intnr>7) return(intnr + 0x68);
else return(intnr+8);
}
/**************************************************************************
void enable_int(BYTE nr)
DESCRIPTION: Enables an IRQ interrupt using the Programmable
interrupt controller (PIC)
To enable a interrupt, you want to turn its PIC bit off.
Let's say that you want to turn on interrupt 3. The
code works something like this:
BIT: 76543210
1 << nr 00001000
~(1 << nr) 11110111 (mask)
01001101 (setting)
setting & mask 01000101
^
|
Notice how bit 3 is set to zero.
**************************************************************************/
void enable_int(BYTE nr)
{
BYTE mask,setting;
if(nr<8) { // First controller?
mask = ~(1 << nr); // Create interrupt mask
setting = inp(0x21) & mask; // Turn off the proper bit
outp(0x21,setting); // Write back the result
}
else { // Second Controller?
mask = ~(1 << (nr-8)); // Create interrupt mask
setting = inp(0xa1) & mask; // Turn off the proper bit
outp(0xa1,setting); // Write back the result
}
}
/**************************************************************************
void disable_int(BYTE nr)
DESCRIPTION: Disables an IRQ interrupt using the Programmable
interrupt controller.
To disable a interrupt, you want to turn its PIC bit on.
Let's say that you want to turn off interrupt 3. The
code works something like this:
BIT: 76543210
1 << nr 00001000
~(1 << nr) 11110111 (mask)
01000101 (setting)
~setting 10111010
(~setting) & mask 10110010 (new value of setting)
~setting 01001101
^
|
Notice how bit 3 is set to one.
**************************************************************************/
void disable_int(BYTE nr)
{
BYTE mask,setting;
if(nr<8) { // First controller?
mask = ~(1 << nr); // Create interrupt mask
setting = (~inp(0x21)) & mask; // Turn on the proper bit
outp(0x21,~setting); // Write back the result
}
else { // Second Controller?
mask = ~(1 << (nr-8)); // Create interrupt mask
setting = (~inp(0xa1)) & mask; // Turn on the proper bit
outp(0xa1,~setting); // Write back the result
}
}
/**************************************************************************
short getvoice(VOICE v[],short track,short channel, short note)
DESCRIPTION: Find the first matching voice (or first inactive voice if
a match is not found). This function is used by midiplayer()
as an interface to get FM voices.
RETURNS:
Open number if successful, -1 if not.
**************************************************************************/
short getvoice(VOICE v[],short track,short channel, short note)
{
short i;
for(i = 0; i < 9; i++) { // find matching active note
if(v[i].active) {
if(v[i].owner_track == track &&
v[i].owner_channel == channel &&
v[i].note == note) return(i);
}
}
// no note, so find first inactive voice
for(i = 0; i < 9; i++) {
if(!v[i].active) return(i);
}
// no available voices... error
return -1;
}
/**************************************************************************
void dotick(void)
DESCRIPTION: Handles ongoing MOD commands at each tick.
**************************************************************************/
void dotick(void)
{
short i,k; // Local variable we need
WORD period,rate;
BYTE e1,e2;
float vibspot;
for(i = 0; i < 4; i++) { // Loop through all four channels
e1 = chan[i].effect; // Get info aboput what is going on
e2 = chan[i].x;
if(chan[i].retrigger) { // Check for a retrigger signal
// Time to retrigger?
if(!(mod_currenttick % chan[i].retrigger)) {
chan[i].position = 0;
chan[i].counter = 0;
chan[i].end = mod_data->slength[chan[i].sample_number-1];
}
}
if(chan[i].cut > -1) { // Check for a cut signal
if(mod_currenttick >= chan[i].cut) chan[i].volume = 0;
}
switch(e1) {
case 0: // Arpeggio
chan[i].count++;
switch(chan[i].count % 3) {
case 0:
chan[i].pinc = chan[i].data1;
break;
case 1:
chan[i].pinc = chan[i].data2;
break;
case 2:
chan[i].pinc = chan[i].data3;
break;
}
break;
case 1: // Slide up
// glissando = chromatic slide
if(!mod_glissando) chan[i].period -= chan[i].porta;
else for(k =0; k < chan[i].porta; k++) chan[i].period *= UP_SEMITONE;
// Don't go too high
if(chan[i].period < 1) chan[i].period = 1;
// Set frequency counters
rate = 7093789.2/(2*chan[i].period);
chan[i].pinc = (rate * 256.0)/sample_rate;
break;
case 2: // slide down
if(!mod_glissando) chan[i].period += chan[i].porta;
// glissando = chromatic slide
else for(k =0; k < chan[i].porta; k++) chan[i].period *= DOWN_SEMITONE;
// Don't go too low
if(chan[i].period > 2000) chan[i].period = 2000;
// Set frequency counters
rate = 7093789.2/(2*chan[i].period);
chan[i].pinc = (rate * 256.0)/sample_rate;
break;
case 3: // slide to note
// Slide up or down? Each case is
// just like regular up and down
// slides.
if(chan[i].period < chan[i].period2) {
if(!mod_glissando) chan[i].period += chan[i].porta;
else for(k =0; k < chan[i].porta; k++) chan[i].period *= DOWN_SEMITONE;
if(chan[i].period > chan[i].period2) chan[i].period = chan[i].period2;
}
else if(chan[i].period > chan[i].period2) {
if(!mod_glissando) chan[i].period -= chan[i].porta;
else for(k =0; k < chan[i].porta; k++) chan[i].period *= UP_SEMITONE;
if(chan[i].period < chan[i].period2) chan[i].period = chan[i].period2;
}
// Set frequency counters
rate = 7093789.2/(2*chan[i].period);
chan[i].pinc = (rate * 256.0)/sample_rate;
break;
case 4: // vibrato
// Calculate current waveform
vibspot = chan[i].vibperiod/32.0 *
(double)mod_currenttick/mod_ticksperdivision * M_PI;
// Adjust the period
period = chan[i].period + chan[i].vibdepth * vibspot;
// Set frequency counters
rate = 7093789.2/(2* period);
chan[i].pinc = (rate * 256.0)/sample_rate;
break;
case 5: // Cont. Slide to note + vol. Slide
if(chan[i].period < chan[i].period2) {
chan[i].period += chan[i].porta;
if(chan[i].period > chan[i].period2) chan[i].period = chan[i].period2;
}
else if(chan[i].period > chan[i].period2) {
chan[i].period -= chan[i].porta;
if(chan[i].period < chan[i].period2) chan[i].period = chan[i].period2;
}
rate = 7093789.2/(2*chan[i].period);
chan[i].pinc = (rate * 256.0)/sample_rate;
chan[i].volume += chan[i].vslide;
if(chan[i].volume < 0) chan[i].volume = 0;
if(chan[i].volume > 64) chan[i].volume = 64;
break;
case 6: // Vibrato + volume slide
vibspot = chan[i].vibperiod/32.0 *
(double)mod_currenttick/mod_ticksperdivision * M_PI;
period = chan[i].period + chan[i].vibdepth * vibspot;
rate = 7093789.2/(2* period);
chan[i].pinc = (rate * 256.0)/sample_rate;
chan[i].volume += chan[i].vslide;
if(chan[i].volume < 0) chan[i].volume = 0;
if(chan[i].volume > 64) chan[i].volume = 64;
break;
case 7: // Tremolo
// Just like Vibrato except with
// this modulates sample volume
vibspot = chan[i].vibperiod/32.0 *
(double)mod_currenttick/mod_ticksperdivision * M_PI;
chan[i].volume = chan[i].volume2 + chan[i].vibdepth * vibspot;
if(chan[i].volume < 0) chan[i].volume = 0;
if(chan[i].volume > 64) chan[i].volume = 64;
break;
case 10: // Volume slide
chan[i].volume += chan[i].vslide; // change the volume.
// Clip.
if(chan[i].volume < 0) chan[i].volume = 0;
if(chan[i].volume > 64) chan[i].volume = 64;
break;
case 14:
switch(e2) {
case 13: // Delay sample
// data4 is decremented on each
// Pass. When it gets to zero,
// the sample volume is turned
// on.
if(!chan[i].data4) chan[i].volume = chan[i].data3;
chan[i].data4--;
break;
default:
break;
}
break;
case 15:
break;
default:
break;
}
chan[i].volume *= channel_select[i]; // THis allows the user to mask
// channels.
}
}
/**************************************************************************
void dodiv(void)
DESCRIPTION: Handles the commands found at a module division.
**************************************************************************/
void dodiv(void)
{
short i,j,k; // Local variables we'll need
short samp,pattern_break = FALSE,position_jump = FALSE,loop = FALSE;
WORD period,rate;
BYTE e1,e2,e3;
float num;
for(i = 0; i < 4; i++) { // Loop through all four channels
chan[i].retrigger = 0; // Reset retrigger and cut signals
chan[i].cut = -1;
// Grab information for this channel
samp = (*(mod_pattern)&0xF0) + ((*(mod_pattern+2) & 0xf0)>>4);
period = (*(mod_pattern)&0x0F)*256 + *(mod_pattern+1);
e1 = (*(mod_pattern+2)) & 0x0F; // e1-e3 are the command nibbles
e2 = (*(mod_pattern+3) & 0xF0) >> 4;
e3 = (*(mod_pattern+3)) & 0x0F;
if(samp && e1 != 3) { // set a new sample?
chan[i].sample_number = samp; // Some MOD commmands need this
chan[i].sdata = mod_data->sdata[samp-1]; // set a pointer to the data
chan[i].position = 0; // Retrigger the sample
chan[i].counter = 0;
// store sample characteristics
chan[i].offset = mod_data->offset[samp-1];
chan[i].rlength = mod_data->repeat[samp-1];
// Keep track of end so we know
// When to stop.
chan[i].end = mod_data->slength[samp-1];
// Set the volume to the default
chan[i].volume = mod_data->volume[samp-1];
}
if(period && e1 != 3) { // Set a new period?
// Calculate adjusted period
chan[i].period = period + mod_data->finetune[chan[i].sample_number-1]
+ mod_finetune;
// Set frequency counter
rate = 7093789.2/(2*period);
chan[i].pinc = (rate*256.0)/sample_rate;
}
chan[i].effect = e1; // Keep for reference in dotick()
chan[i].x = e2;
chan[i].y = e3;
switch(e1) { // Process the commands
case 0: // Arpeggio
chan[i].data1 = chan[i].pinc; // First frequncy
num = chan[i].pinc; // calculate second frequency
for(j = 0; j < e2; j++) num *= UP_SEMITONE;
chan[i].data2 = (WORD)num;
num = chan[i].pinc; // calculate third frequncy
for(j = 0; j < e3; j++) num *= UP_SEMITONE;
chan[i].data3 = (WORD)num;
chan[i].count = 0;
break;
case 1: // Slide up.
chan[i].porta = e2*16 + e3; // Calculate slide rate
break;
case 2: // slide down
chan[i].porta = e2*16 + e3; // calculate slide rate
break;
case 3: // Slide to note
if(e2 || e3) chan[i].porta = e2*16 + e3;
if(period) chan[i].period2 = period;
break;
case 4: // Vibrato (My implementation
// Of this command assumes that
// The wave form is a triggered
// Sin wave. There are other
// forms defined , but they are
// use very rarely and I dont't
// feel like coding them in.
// These numbers will be used
// to calculate the waveform.
if(e2) chan[i].vibperiod = e2* mod_ticksperdivision;
if(e3) chan[i].vibdepth = ((e3/16.0) * 0.0595 ) * chan[i].period;
break;
case 5: // Continue Slide to Note + Volume Slide
case 6: // Continue Vibrato + volume slide
// For both of these commands,
// We just need to keep track
// of the volume slide rate
if((e2 > 0 && e3 > 0) || e3 == 0) chan[i].vslide = e2;
else chan[i].vslide = -e3;
// If the period is specified,
// Then the volume needs to be
// reset.
if(period)chan[i].volume = mod_data->volume[chan[i].sample_number-1];
break;
case 7: // Tremolo (My implementation
// Of this command assumes that
// The wave form is a triggered
// Sin wave. There are other
// forms defined , but they are
// use very rarely and I dont't
// feel like coding them in.
// These values will be used to
// claculate the wave form
if(e2) chan[i].vibperiod = e2* mod_ticksperdivision;
if(e3) chan[i].vibdepth = e3;
chan[i].volume2 = chan[i].volume;
break;
case 8: // UNUSED
break;
case 9: // Set new sample offset
mod_data->offset[chan[i].sample_number-1] = (e2*4096+e3*256)*2;
break;
case 10: // Volume slide
// Calculate slide rate
if((e2 > 0 && e3 > 0) || e3 == 0) chan[i].vslide = e2;
else chan[i].vslide = -e3;
if(period)chan[i].volume = mod_data->volume[chan[i].sample_number-1];
break;
case 11: // Table position jump
mod_tablepos = e2*16+e3;
position_jump = TRUE;
break;
case 12: // Set Volume
chan[i].volume = e2*16+e3;
break;
case 13: // Pattern Break
pattern_break = TRUE; // We'll do the break later.
break;
case 14:
switch(e2) {
case 0: // Set filter. I just ignore this
// since it is a hardware option
break;
case 1: // Fineslide up
// Recalculate frequency counter
chan[i].period -= e3;
rate = 7093789.2/(2* chan[i].period);
chan[i].pinc = (rate*256.0)/sample_rate;
break;
case 2: // Fineslide down
// Recalculate frequncy counter
chan[i].period += e3;
rate = 7093789.2/(2* chan[i].period);
chan[i].pinc = (rate*256.0)/sample_rate;
break;
case 3: // Glissando on/off
// If this is on, slide-to-note
// functions should slide by
// semitones rather than smooth
// slides. This is rarely used.
mod_glissando = e3;
break;
case 4: // Vibrato waveform. This is rarely
// used, so for now, the code is
// locked in to use the default
// waveform: 0 (a triggered sin
// wave)
break;
case 5: // Set sample finetune value
k = e3;
if (k > 7) k -= 16;
mod_data->finetune[chan[i].sample_number-1] = k;
break;
case 6: // Loop Pattern
// mod_loopcount is a global
// used to keep track of the loop.
if(mod_loopcount == -1) {
if(!e3) mod_loopspot = mod_pattern;
else {
if(!mod_loopspot) mod_loopspot =
mod_data->pattern_data[mod_data->ptable[mod_tablepos]];
mod_loopcount = e3;
loop = TRUE;
}
}
else if(e3){
if(!mod_loopcount) mod_loopspot = NULL;
else loop = TRUE;
mod_loopcount--;
}
break;
case 7: // Termolo waveform. (see case #4)
break;
case 8: // UNUSED
break;
case 9: // Retrigger sample
chan[i].retrigger = e3; // Set the retrigger signal used
// in dotick().
break;
case 10: // Fineslide volume up
chan[i].volume += e3;
if(chan[i].volume > 64) chan[i].volume = 64;
break;
case 11: // Fineslide volume down
chan[i].volume -= e3;
if(chan[i].volume < 0) chan[i].volume = 0;
break;
case 12: // Cut Sample
chan[i].cut = e3; // Set cut signal for dotick()
break;
case 13: // Delay sample
chan[i].data4 = e3; // Set counter for dotick()
chan[i].data3 = chan[i].volume;
chan[i].volume = 0; // Turn off volume
break;
case 14: // Pattern Delay
mod_patterndelay = e3;
break;
case 15: // Invert Loop. THis is too much
// of a headache to worry about
// right now. Sorry.
break;
default: // Shouldn't ever get here
break;
}
break;
case 15: // Set Speed.
// There are two possible actions
// with this command: change
// ticks per beat, or change
// beats per minute. Right
// now, only the ticks per beat
// have been implemented.
j = e2*16+e3;
if(j <= 32) {
if(!j) j = 1;
mod_ticksperdivision = j;
}
break;
default: // shouldn't happen.
break;
}
mod_pattern += 4; // Advance to next channel
}
if(loop) { // Loop? Jump back to placeholder!
mod_pattern = mod_loopspot;
}
else if(pattern_break) { // Pat. break? Jump to next pattern!
mod_tablepos++;
if(mod_tablepos == mod_data->num_positions) mod_on = FALSE;
else {
mod_pattern = mod_data->pattern_data[mod_data->ptable[mod_tablepos]];
mod_pstop = mod_pattern+1024;
}
}
else if(position_jump) { // Position jump? Go to specified
// Table.
if(mod_tablepos == mod_data->num_positions) mod_on = FALSE;
else {
mod_pattern = mod_data->pattern_data[mod_data->ptable[mod_tablepos]];
mod_pstop = mod_pattern+1024;
}
}
}
/**************************************************************************
MidiPlayer()
DESCRIPTION: Routine for playing midi files. THis is designed to be
called from a timer interrupt. To use, set these values
in this order:
midi_data (must point to a filled MIDI structure.)
midi_reset = TRUE;
midi_on = TRUE;
The interrupt should pick up from there.
It is easy to add functionality to this routine. I've
already included code to flag a wide variety of MIDI
events, so all you have to do is add your own code under
the point an event is flagged. I've left a bunch of
commented print statements in to help make the code
more readable and provide cues for accessing the data.
*** WARNING ***
If you add your own code here, make sure that it doesn't
take more than a few milliseconds to execute. If
MidiPlayer() is called again by the interrupt before your
code is done, your whole program will probably crash.
**************************************************************************/
void MidiPlayer(void)
{
static VOICE v[9]; // Nine FM voices
VOICE vh[9]; // waiting list
short vhold = 0,spot;
static short i,j,live_tracks,vidx;
static short divisions = 96,ms_per_div=5000;
static BYTE event,ev,ch,b1,b2,etype,track_on[16];
static last_ev[16];
static WORD trkloc[16];
static int itmr,l2,length;
static float tmr[16];
static char tdata[256];
if(!midi_data) { // must have data to play!
midi_on = FALSE;
return;
}
if(midi_reset) { // Reset? zero track pointers and timers
for(i = 0; i < 16; i++ ) {
trkloc[i] = 1; // no need to read first time offset
tmr[i] = 0; // all timers start at zero
track_on[i] = TRUE;
if(i < 9) v[i].active = 0; // unreserve all voices
last_ev[i] = 0x80; // set last event to note off
}
midi_reset = 0; // clear midi reset flag
live_tracks = midi_data->num_tracks;// set number of active tracks so
// we know when to stop.
divisions = midi_data->divisions; // ticks per quarter note
if(divisions < 0) divisions = -divisions; // some midi files have
// negative division values
}
if(!midi_on) return; // logical switch for midi on/off
for(i = 0 ; i < midi_data->num_tracks; i++) { // loop over tracks
while(tmr[i] <= 0) { // Process while timer is 0;
event = *(TD); // get next event (TD is a macro)
trkloc[i]++; // advance track location pointer
if(event == 0xFF) { // META event?
etype = *(TD);
trkloc[i] ++;
// read length of meta event
spot = ReadVarLen(TD,&length);
trkloc[i] += spot;
// grab any text data for text events
for(j = 0; j < length; j++) tdata[j] = *(TD + j);
tdata[j] = 0;
switch(etype) {
case 0x00:
j = *(TD)*256 + *(TD+1);
//printf("[%d] SEQUENCE NUMBER (%d)\n",i,j);
break;
case 0x01:
//printf("[%d] TEXT EVENT (%s)\n",i,tdata);
break;
case 0x02:
//printf("[%d] COPYWRITE EVENT (%s)\n",i,tdata);
break;
case 0x03:
//printf("[%d] TRACK NAME EVENT (%s)\n",i,tdata);
break;
case 0x04:
//printf("[%d] INSTRUMENT NAME EVENT (%s)\n",i,tdata);
break;
case 0x05:
//printf("[%d] LYRIC EVENT (%s)\n",i,tdata);
break;
case 0x06:
//printf("[%d] MARKER EVENT (%s)\n",i,tdata);
break;
case 0x07:
//printf("[%d] CUE EVENT (%s)\n",i,tdata);
break;
case 0x2f: // End of track
//printf("[%d] END OF TRACK\n",i);
tmr[i] = MAXFLOAT; // set timer to highest value
track_on[i] = FALSE; // turn off track
live_tracks--; // decrement track counter
if(live_tracks == 0) { // last track? Turn off midi!
midi_on = FALSE;
midi_reset = TRUE; // Make sure we start over
return;
}
break;
case 0x51: // TEMPO event (microsecs per 1/4 note)
l2 = *(TD) * 0x10000L + *(TD+1) * 0x100 + *(TD+2);
//printf("[%d] TEMPO EVENT (%ld)\n",i,l2);
ms_per_div = (short)(l2/divisions);
// Convert number to a counter used
// by an 183 Hhz interrupt.
midi_callfreq = ms_per_div/5454.0;
break;
case 0x58:
//printf("[%d] TIME SIG EVENT (%X,%X,%X,%X)\n",i,
// *(TD),*(TD+1),*(TD+2),*(TD+3));
break;
case 0x59:
//printf("[%d] KEY SIG EVENT (%X,%X)\n",i,*(TD),*(TD+1));
break;
case 0x7F:
//printf("[%d] SEQUENCER DATA EVENT\n",i);
break;
default:
//printf("[%d] *** undefined event *** (%X,type: %X,length %ld)\n",i,event,etype,length);
break;
}
trkloc[i] += (WORD)length;
}
else if(event == 0xF0 || event == 0xF7) { // sysex event
trkloc[i] += ReadVarLen(TD,&length);
//printf("Sysex type 1 [length: %ld]\n",length);
trkloc[i] += (WORD)length;
}
else { // PROCESS MIDI EVENTS
if(!(event & 0x80)) { // top bit Not set? Running status!
b1 = event; // b1 = note (usually)
b2 = *(TD + 1); // b2 = volume? (usually)
event = last_ev[i]; // use last event
//printf("Running status >>");
//for(j = 0; j < 9; j++) printf("%d",v[j].active);
//printf("\n");
trkloc[i] --; // one less byte for running status.
if(midi_mpuout) {
ev = event & 0xF0;
MPU_Write(event);
MPU_Write(b1);
if(ev != 0xC0 && ev != 0xD0) MPU_Write(b2);
}
}
else { // Else it was a regular event
ev = event & 0xF0;
event = ev + midi_patchmap[ch];
last_ev[i] = event; // set to last event
b1 = *(TD); // get next two bytes
b2 = *(TD+1);
if(midi_mpuout) {
MPU_Write(event);
MPU_Write(b1);
if(ev != 0xC0 && ev != 0xD0) MPU_Write(b2);
}
}
ev = event & 0xF0; // strip lower four bits
ch = event & 0x0f; // channel
vidx = getvoice(v,i,ch,b1); // Get a voice index
switch(ev) {
case 0x80: // Note off
//printf("[%d] Note off (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
if(vidx > -1) { // If a matching voice was found,
// kill it.
FM_KeyOff(vidx);
FM_SetVol(vidx,0);
v[vidx].active = FALSE;
}
break;
case 0x90: // Note On
//printf("[%d] Note on (%X,%d,%d)\n",i,event,b1,b2);
//printf("%X",ch);
trkloc[i] += 2;
if(vidx > -1) { // Voice found?
if(v[vidx].active) { // already active? Turn it off.
v[vidx].active = FALSE;
FM_KeyOff(vidx);
FM_SetVol(vidx,0);
}
else if(midi_fmout){ // Wasn't active? Turn it on.
v[vidx].owner_track = i;
v[vidx].owner_channel = ch;
v[vidx].note = b1;
v[vidx].volume = b2;
v[vidx].active = TRUE;
FM_SetNote(vidx,b1);
FM_SetVol(vidx,music_volume);
FM_KeyOn(vidx);
}
}
else { // There might be space later
// store our note
vh[vhold].owner_track = i;
vh[vhold].owner_channel = ch;
vh[vhold].note = b1;
vh[vhold].volume = b2;
vhold ++;
if(vhold >8) vhold = 8; // Only nine hold notes considered
}
break;
case 0xA0: // Key pressure
//printf("[%d] Note presure (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
break;
case 0xB0: // Control CHange
//printf("[%d] Control Change (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
break;
case 0xC0: // Program change
//printf("[%d] Program change (%d)\n",i,b1);
trkloc[i] += 1;
if(midi_mpuout) {
MPU_Write(event);
MPU_Write(b1);
}
break;
case 0xD0: // Channel Pressure
//printf("[%d] Channel Pressure (%d,%d)\n",i,b1);
trkloc[i] += 1;
break;
case 0xE0: // Pitch wheel change
//printf("[%d] Pitch change (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
if(midi_mpuout) {
MPU_Write(event);
MPU_Write(b1);
MPU_Write(b2);
}
break;
default: // Uh-OH
//printf("MIDI ERROR (F0 midi command)\n");
midi_on = FALSE;
return;
}
}
// read next time offset
if(track_on[i]) {
trkloc[i] += ReadVarLen(TD,&itmr);
tmr[i] += itmr;
//printf(" T: %ld\n",tmr[i]);
}
}
tmr[i]-= 1.0 * midi_tempoadjust; // decrement timer
}
// Since there is a limited number
// of FM voices, some notes do
// not get voiced. This next
// section takes a list of
// unallocated notes and tries
// to find a spot for them.
while(vhold) {
vhold--; // go to next note
for(i = 0; i < 9; i++) { // loop through FM voices
if(!v[i].active) { // found empty one? set the note!
v[i].owner_track = vh[vhold].owner_track;
v[i].owner_channel = vh[vhold].owner_channel;
v[i].note = vh[vhold].note;
v[i].volume = vh[vhold].volume;
v[i].active = TRUE;
FM_SetNote(i,b1);
FM_SetVol(i,music_volume);
FM_KeyOn(i);
break;
}
}
if(i == 9) vhold = 0; // List full? forget about other notes
}
}
/**************************************************************************
ReadMidi(char *filename, MIDI *mdata, char *errstring)
DESCRIPTION: Reads a midi file and stores it to a MIDI data structure
INPUTS:
filename Pointer to full midi filename
midipoint Indirect pointer to empty midi pointer
errstring Pointer to a pre-allocated string.
Outputs:
returns 0 if successful.
On error, it returns a number and fills errstring with the
error message.
**************************************************************************/
short ReadMidi(char *filename, MIDI **midipoint, char *errstring)
{
short i;
FILE *input;
char sdata[256];
int lidata;
short idata;
DWORD lread;
MIDI *mdata;
input = fopen(filename,"rb"); // open a midi file
if(!input) {
sprintf(errstring,"cannot open %s",filename);
return(1);
}
// Read the header
fread(sdata,1,4,input); // midi id there?
sdata[4] = 0;
if(strcmp(sdata,"MThd")) {
sprintf(errstring,"Not a midi file.");
fclose(input);
return(2);
}
// printf("Chunk type: %s\n",sdata);
lidata = ReadLong(input); // length of remaining header chunk?
// printf("Chunk length: %ld\n",lidata);
if(lidata > 250) {
sprintf(errstring,"Header chunk has a weird length");
exit(0);
}
mdata = (MIDI *)malloc(sizeof(MIDI)); // make room for music!
if(!mdata) {
sprintf(errstring,"Out of memory.");
fclose(input);
return(3);
}
*midipoint = mdata; // Assign our pointer
idata = ReadShort(input); // Format
//printf("Format: %d\n",idata);
if(idata != 0 && idata != 1) {
sprintf(errstring,"Unrecognized MIDI format");
fclose(input);
return(4);
}
mdata->format = idata;
idata = ReadShort(input); // number of tracks
//printf("# of Tracks: %d\n",idata);
if(idata < 1 || idata > 16) {
sprintf(errstring,"Bad number of tracks [%d]",idata);
fclose(input);
return(5);
}
mdata->num_tracks = idata;
idata = ReadShort(input); // division number (tempo)
//printf("1/4 note division: %d\n",idata);
mdata->divisions = abs(idata);
// Read individual track data
for(i = 0; i < mdata->num_tracks; i++) {
fread(sdata,1,4,input); // midi track id there?
sdata[4] = 0;
if(strcmp(sdata,"MTrk")) {
sprintf(errstring,"Error reading track #%d",i);
fclose(input);
return(6);
}
//printf("Chunk type: %s\n",sdata);
lidata = ReadLong(input); // length of remaining track chunk?
//printf("Chunk length: %ld\n",lidata);
// Allocate space for track
mdata->track[i] = (BYTE *)malloc(lidata);
if(!mdata->track[i]) {
sprintf(errstring,"Out of memory.");
fclose(input);
return(3);
}
// read in entire track
lread = fread(mdata->track[i],1,(size_t)lidata,input);
if(lread < lidata) {
sprintf(errstring,"Premature end of midi file [track %d]",i);
fclose(input);
return(7);
}
}
fclose(input);
return 0;
}
/**************************************************************************
short ReadVarLen(char *data,int *value)
DESCRIPTION: Reads a variable length long interger from data string.
This functionis used by ReadMidi() to grab special numbers
from MIDI data files.
**************************************************************************/
short ReadVarLen(BYTE *data,int *value)
{
short i=0;
BYTE c;
if ((*value = *(data + i)) & 0x80) {
*value &= 0x7f;
do {
i++;
*value = (*value << 7) + ((c = *(data +i)) & 0x7f);
} while (c & 0x80);
}
return(i+1); // return number of bytes read
}
/**************************************************************************
int ReadShort(FILE *inflile)
DESCRIPTION: Reads a short interger from a file
**************************************************************************/
short ReadShort(FILE *infile)
{
return (fgetc(infile) << 8) | fgetc(infile);
}
/**************************************************************************
int ReadLong(FILE *inflile)
DESCRIPTION: Reads a long interger from a file
**************************************************************************/
int ReadLong(FILE *infile)
{
short i;
int num = 0;
num = (unsigned char)fgetc(infile);
for(i = 0; i < 3; i++) {
num = (num << 8) | (unsigned char)fgetc(infile);
}
return(num);
}
/**************************************************************************
void playsound(SAMPLE *data,length)
DESCRIPTION: Adds a sound to the play list. If the playlist is full,
all the sounds are scooted over and the new sound is added
as the last item;
**************************************************************************/
void playsound(SAMPLE *data,DWORD length)
{
short i;
if(sounds_in_queue >= MAXSOUNDS) { // List full?
for(i= 0; i <sounds_in_queue; i++) { // Scoot all the sounds down and
// discard sound 0.
sounddata[i] = sounddata[i+1];
soundpos[i] = soundpos[i+1];
}
sounds_in_queue--;
}
sounddata[sounds_in_queue] = data; // Add the new sound
soundpos[sounds_in_queue] = length-1;
sounds_in_queue++;
}
/**************************************************************************
MOD *loadmod(char *modfilename)
DESCRIPTION: Loads a Module file (Noise,Sound, or Protracker)
**************************************************************************/
MOD *loadmod(char *modfilename)
{
short i,j,ft;
FILE *input;
MOD *m;
char signature[5];
short samples = 15;
m = (MOD *)malloc(sizeof(MOD)); // Allocate the skeleton MOD struct
if(!m) {
return(NULL);
}
input = fopen(modfilename,"rb"); // access the file
if(!input) {
free(m);
return(NULL);
}
fseek(input,1080, SEEK_SET); // Look for signatures so we
// can tell of this MOD has 15
// Or 31 samples.
fread(signature,1,4,input);
signature[4] = 0;
if(!strcmp(signature,"M.K.")) samples = 31;
else if(!strcmp(signature,"M!K!")) samples = 31;
else if(!strcmp(signature,"FLT4")) samples = 31;
rewind(input); // Go back to the start.
fread(m->title,1,20,input); // read the module title
m->num_samples = 0;
for(i = 0; i < samples; i++) { // read the sample headers
fread(m->sample_name[i],1,22,input); // name
// length (convert words to bytes)
m->slength[i] = (fgetc(input) * 256+ fgetc(input))*2;
ft = fgetc(input)&0x0f; // finetune
if(ft>7) ft -=16; // Convert to a signed value
m->finetune[i] = ft;
m->volume[i] = fgetc(input); // Sample volume
// repeat offset (words -> bytes)
m->offset[i] = (fgetc(input) * 256 + fgetc(input))*2;
// repeat length (words -> bytes)
m->repeat[i] = (fgetc(input) * 256 + fgetc(input))*2;
if(m->repeat[i] == 2) m->repeat[i] = 0;
// Sometimes the repeat length
// and offset are not set properly,
// so we have to do a little
// patching here.
if(m->offset[i] + m->repeat[i] > m->slength[i] ) {
if(m->repeat[i] > m->slength[i]) {
m->repeat[i] = m->slength[i];
m->offset[i] = 0;
}
else {
m->offset[i] -= (m->repeat[i] + m->offset[i] - m->slength[i]);
}
}
if(m->slength) m->num_samples++; // Keep track of the number of real smaples
}
m->num_positions = fgetc(input); // number of patterns to play
fgetc(input); // unused byte
fread(m->ptable,1,128,input); // read pattern table
if(samples == 31) {
fread(m->sig,1,4,input); // read module signature
m->sig[4] = 0;
}
else strcpy(m->sig,"NONE");
m->maxpattern = 0;
for(i = 0; i < m->num_positions; i++) { // Find number of unique patterns
if(m->ptable[i] > m->maxpattern) m->maxpattern = m->ptable[i];
}
for(i=0;i<128;i++) m->pattern_data[i]=NULL; // Reset pattern buffer pointers
for(i = 0; i <= m->maxpattern; i++) { // read in 1024-byte patterns
m->pattern_data[i] = (BYTE *)malloc(1024);
if(!m->pattern_data[i]) { // Did we get the memory?
// If not, free what we have
// and go home.
for(j = 0; j < i; j++) free(m->pattern_data[j]);
free(m);
return(NULL);
}
fread(m->pattern_data[i],1,1024,input); // Read in the pattern
}
// Reset sample data pointers
for(i = 0; i < 32; i++) m->sdata[i] = NULL;
for(i = 0; i <samples; i++) { // Read in the sample data
m->sdata[i] = NULL; // Reset sample
if(!m->slength[i]) continue; // Continue if nothing to read
m->sdata[i] = (SAMPLE *)malloc(m->slength[i]+1);
if(!m->sdata[i]) { // Did we get the memory?
// If not, free what we have
// and go home.
for(j = 0; j <= m->maxpattern; j++) free(m->pattern_data[j]);
for(j = 0; j < i; j++) free(m->sdata[j]);
free(m);
return(NULL);
}
// Read in the sample
fread(m->sdata[i],1,m->slength[i],input);
}
fclose(input); // All done!
return(m); // Give the new Module back to
// the caller.
}
/*********************************************************************
SAMPLE *loadwave(char *wavefile,unsigned int *length)
DESCRIPTION: Loads a wave files (mono, 8bit)
INTPUTS:
wavefile filename of wave file
length pointer to length interger
RETURNS:
pointer to data
**********************************************************************/
SAMPLE *loadwave(char *wavefile,unsigned int *length)
{
short i;
SAMPLE *data;
BYTE dummydata[255];
FILE *input;
DWORD rlen,flen;
WORD s_per_sec,b_per_sec,num_channels,tag;
char riffid[5],waveid[5],fmtid[5],dataid[5];
input = fopen(wavefile,"rb");
if(!input) { // If unsuccesful...
*length = 1; // set short length to prevent
// mistakes later.
return(NULL); // REturn a null pointer
}
// Get WAVE header data
fread(riffid,1,4,input); // wave files staqrt with "Riff"
riffid[4] = 0;
fread(&rlen,1,4,input); // File size
fread(waveid,1,4,input); // Wave id string ("Wave")
waveid[4] = 0;
if(strcmp(waveid,"WAVE")) { // is it a wave file?
fclose(input);
return(NULL);
}
fread(fmtid,1,4,input); // Format id string ("fmt ")
fmtid[4] = 0;
fread(&flen,1,4,input); // offset to data
if(flen > 240) flen = 240; // Just a precaution so that
// We do not overload dummydata
fread(&tag,1,2,input); // tag
fread(&num_channels,1,2,input); // number of channels
fread(&s_per_sec,1,2,input); // sample rate (hz)
fread(&b_per_sec,1,2,input); // bytes per seconf rate
fread(dummydata,1,(size_t)flen-8,input); // Skip ahead
fread(dataid,1,4,input); // Dataid string
dataid[4] = 0;
fread(length,1,4,input); // length of data
data = (SAMPLE *)malloc(*length+1); // allocate memory for data
if(!data) { // oops. Not enough mem!
fclose(input);
return(NULL);
}
fread(data,1,(size_t)*length,input); // read the data
for(i = 0; i < *length; i++) { // convert to signed format
*(data + i) = ((BYTE)*(data + i))-128;
}
fclose(input); // Wrap it up
return(data);
}
/**************************************************************************
void load_instruments(char *filename,BYTE inst[128][11])
DESCRIPTION: Loads instrument defs from a file (128 total)
File format: 11 hex values followed by a name. eg:
30 33 40 00 E1 E2 87 63 06 01 00 "Electric Piano 2"
33 34 00 00 92 C3 C3 B3 02 01 00 "Harpsichord"
32 32 00 00 92 C3 C3 B3 02 01 00 "Clavichord"
.
.
.
(The text at the end is ignored.)
The hex values are dumped into an 2-D array. The file can have
more or less than 128 defs without harm to this function.
**************************************************************************/
short load_instruments(char *filename,BYTE inst[128][11])
{
FILE *input;
short i=0,j;
char string[255];
input = fopen(filename,"r"); // open the file
if(!input) return(0);
// read it's contents
while(fgets(string,255,input) && i < 128) {
for(j = 0; j < 11; j++) sscanf(string+j*3,"%X ",&inst[i][j]);
i++;
}
// clean up and go home
fclose(input);
return(i);
}
/**************************************************************************
void freemidi(MIDI *m)
DESCRIPTION: Frees the data allocated for a MIDI structure
**************************************************************************/
void freemidi(MIDI *m)
{
short i;
for(i = 0; i < m->num_tracks; i++) free(m->track[i]);
free(m);
}
/**************************************************************************
void freemod(MOD *freeme)
DESCRIPTION: Frees all the memory associated with a MOD file.
BYTE far *pattern_data[128];
SAMPLE *sdata[32];
**************************************************************************/
void freemod(MOD *freeme)
{
short i;
for(i = 0; i <32; i++) { // Free the samples
if(freeme->sdata[i]) free(freeme->sdata[i]);
}
for(i = 0; i <128; i++) { // Free the pattern data
if(freeme->pattern_data[i]) free(freeme->pattern_data[i]);
}
free(freeme);
}
/**************************************************************************
void VarmintVSync(void)
DESCRIPTION: This vsync function pays attention to vsyncclock so that we
never miss a vsync. The vsync can be missed if sb_int
gets called while we are waiting for the retrace.
The variable vsync_toolong should be roughly equalt to
the number of times you expect sb_int to get called between
vertical retraces. The will be afffected by dma_bufferlen,
sample_rate, and the frequency of the VGA monitor
**************************************************************************/
void VarmintVSync(void)
{
while(!(inp(0x3da)&0x08)) { // Wait for retrace to start
if(vsyncclock>vsync_toolong) break; // Have we waited too long?
}
while(inp(0x3da)&0x08); // Wait for retrace to finish
vsyncclock = 0; // reset the clock
}
// This function allocates memory from the low 640K
// At this point no error checking is done to verify it works
void *real_malloc(int size,int* handle)
{
union REGS dos;
dos.x.eax=0x100; //Function in DPMI to allocate REAL MEMORY
dos.x.ebx=(size+15)>>4; //change it to 16 byte block
int386(0x31,&dos,&dos);
//AX holds Segment
//DX holds base selector of allocated block
if (dos.x.cflag)
return(NULL);
*handle=dos.w.dx;
return((void*)((dos.x.eax &0xffff)<<4));
//Change segement to a protected mode pointer
}
// This function frees up memory allocated by the real malloc function
void real_free(int handle)
{
union REGS dos;
dos.w.ax=0x101;
dos.w.dx=handle;
int386(0x31,&dos,&dos);
}